#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

Strict

Import mojo
Import diddy
Import diddy.gui.core

Const SKIN_NODE_NONE:String = ""
Const SKIN_NODE_WINDOW:String = "window"
Const SKIN_NODE_BUTTON:String = "button"
Const SKIN_NODE_RADIO:String = "radio"
Const SKIN_NODE_CHECKBOX:String = "checkbox"
Const SKIN_NODE_SLIDER:String = "slider"
Const SKIN_NODE_LABEL:String = "label"

Class Component
Private
' Private fields
	Field alpha:Float = 1
	Field children:DiddyStack<Component> = New DiddyStack<Component>
	Field childrenZOrder:DiddyStack<Component> = New DiddyStack<Component>
	
	' external listeners (properties) - these can and should be changed by developers for their own needs
	Field mouseListener:IMouseListener
	Field mouseMotionListener:IMouseMotionListener
	Field keyListener:IKeyListener
	Field focusListener:IFocusListener
	Field actionListener:IActionListener
	
	' internal listeners - these can't and shouldn't be changed by developers!!!
	Field internalMouseListener:IMouseListener
	Field internalMouseMotionListener:IMouseMotionListener
	Field internalKeyListener:IKeyListener
	Field internalFocusListener:IFocusListener
	Field internalActionListener:IActionListener
	
	Field layoutManager:ILayoutManager
	Field layoutData:ILayoutData
	
	Field fontName:String = "Tahoma-10"
	
	Field styleNormal:ComponentStyle = New ComponentStyle
	
	Field visible:Bool = True
	Field enabled:Bool = True
	Field focusable:Bool = False
	
	' sizes for layout
	Field preferredWidth:Int
	Field preferredHeight:Int
	Field minimumWidth:Int
	Field minimumHeight:Int
	
	Field parent:Component
	
	Field x:Int, y:Int
	Field w:Int, h:Int
	Field w2:Float, h2:Float
	Field zOrderLocked:Bool = False

' Private methods
	Method RequestFocusDelegate:Bool(thisGui:GUI)
		' check the last focused child
		If focusedChild <> Null Then
			' check focusable first to save a method call
			If focusedChild.focusable Then
				thisGui.SetFocus(focusedChild)
				Return True
			End
			' recurse
			If focusedChild.RequestFocusDelegate(thisGui) Then Return True
		End
		' find first focusable child
		For Local i:Int = 0 Until children.Count()
			Local child:Component = children.Get(i)
			If child.focusable Then
				thisGui.SetFocus(child)
				Return True
			End
		Next
		' request focus on the children
		For Local i:Int = 0 Until children.Count()
			Local child:Component = children.Get(i)
			If child <> focusedChild And child.RequestFocusDelegate(thisGui) Then Return True
		Next
		' we still couldn't focus
		Return False
	End

	Method ReadSkinFields:Void(node:XMLElement)
	End
	
	' Reads the styles from the XML node.
	Method LoadStyles:Void(node:XMLElement)
		If node = Null Then Return
		' copy styles
		For Local i:Int = 0 Until node.Children.Count()
			Local styleNode:XMLElement = node.Children.Get(i)
			Local style:ComponentStyle = GetStyle(styleNode.GetAttribute("name"))
			If style = Null Then style = New ComponentStyle
			style.ReadFromNode(styleNode)
			SetStyle(styleNode.GetAttribute("name"), style)
		Next
	End
	
Public
	' Public Fields
	' Note that these are public for now so they can be referenced from core.monkey
	' Please don't use them directly!!!
	Method ApplySkin:Void()
		Local thisGUI:GUI = FindGUI()
		Local node:XMLElement = thisGUI.GetSkinNode(GetSkinNodeName())
		If node <> Null Then
			LoadStyles(node)
			ReadSkinFields(node)
		End
	End
	

	Field mouseHover:Bool = False
	Field mouseDown:Bool = False
	Field focusedChild:Component
	
' Properties
	' X is read/write
	Method X:Int() Property
		Return x
	End
	Method X:Void(x:Int) Property
		Self.x = x
	End
	
	' Y is read/write
	Method Y:Int() Property
		Return y
	End
	Method Y:Void(y:Int) Property
		Self.y = y
	End
	
	' Width is read/write and updates w2
	Method Width:Int() Property
		Return w
	End
	Method Width:Void(width:Int) Property
		Self.w = width
		Self.w2 = width/2.0
	End
	
	' Height is read/write and updates h2
	Method Height:Int() Property
		Return h
	End
	Method Height:Void(height:Int) Property
		Self.h = height
		Self.h2 = height/2.0
	End
	
	' LeftEdge is read/write and updates x, w, and w2
	Method LeftEdge:Int() Property
		Return x
	End
	Method LeftEdge:Void(leftEdge:Int) Property
		Local offset:Int = x-leftEdge
		Self.x = leftEdge
		Self.w += offset
		Self.w2 = Self.w/2.0
	End
	
	' RightEdge is read/write and updates w and w2
	Method RightEdge:Int() Property
		Return x+w
	End
	Method RightEdge:Void(rightEdge:Int) Property
		Self.w = rightEdge-Self.x
		Self.w2 = Self.w/2.0
	End
	
	' TopEdge is read/write and updates y, h, and h2
	Method TopEdge:Int() Property
		Return y
	End
	Method TopEdge:Void(topEdge:Int) Property
		Local offset:Int = y-topEdge
		Self.y = topEdge
		Self.h += offset
		Self.h2 = Self.h/2.0
	End
	
	' BottomEdge is read/write and updates h and h2
	Method BottomEdge:Int() Property
		Return y+h
	End
	Method BottomEdge:Void(bottomEdge:Int) Property
		Self.h = bottomEdge-Self.y
		Self.h2 = Self.h/2.0
	End
	
	' Parent is read only
	Method Parent:Component() Property
		Return parent
	End
	
	' FontName is read/write
	Method FontName:String() Property
		Return fontName
	End
	Method FontName:Void(fontName:String) Property
		Self.fontName = fontName
	End
	
	' Children is read only
	Method Children:DiddyStack<Component>() Property
		Return children
	End
	
	' ChildrenZOrder is read only
	Method ChildrenZOrder:DiddyStack<Component>() Property
		Return childrenZOrder
	End
	
	' LayoutManager is read/write
	Method LayoutManager:ILayoutManager() Property
		Return layoutManager
	End
	Method LayoutManager:Void(layoutManager:ILayoutManager) Property
		Self.layoutManager = layoutManager
	End
	
	' LayoutData is read/write
	Method LayoutData:ILayoutData() Property
		Return layoutData
	End
	Method LayoutData:Void(layoutData:ILayoutData) Property
		Self.layoutData = layoutData
	End
	
	' ActionListener is read/write
	Method ActionListener:Void(actionListener:IActionListener) Property
		Self.actionListener = actionListener
	End
	Method ActionListener:IActionListener() Property
		Return Self.actionListener
	End
	
	' MouseListener is read/write
	Method MouseListener:Void(mouseListener:IMouseListener) Property
		Self.mouseListener = mouseListener
	End
	Method MouseListener:IMouseListener() Property
		Return Self.mouseListener
	End

	' MouseMotionListener is read/write
	Method MouseMotionListener:Void(mouseMotionListener:IMouseMotionListener) Property
		Self.mouseMotionListener = mouseMotionListener
	End
	Method MouseMotionListener:IMouseMotionListener() Property
		Return Self.mouseMotionListener
	End
	
	' KeyListener is read/write
	Method KeyListener:Void(keyListener:IKeyListener) Property
		Self.keyListener = keyListener
	End
	Method KeyListener:IKeyListener() Property
		Return Self.keyListener
	End

	' FocusListener is read/write
	Method FocusListener:Void(focusListener:IFocusListener) Property
		Self.focusListener = focusListener
	End

	Method FocusListener:IFocusListener() Property
		Return Self.focusListener
	End

	' StyleNormal is read/write
	Method StyleNormal:ComponentStyle() Property
		If styleNormal = Null Then styleNormal = New ComponentStyle
		Return styleNormal
	End
	Method StyleNormal:Void(style:ComponentStyle) Property
		AssertNotNull(style, "StyleNormal may not be null.")
		styleNormal = style
	End
	
	' Focusable is read/write
	Method Focusable:Bool() Property
		Return focusable
	End
	Method Focusable:Void(focusable:Bool) Property
		Self.focusable = focusable
	End
	
	' Focused is read only (use RequestFocus instead)
	Method Focused:Bool() Property
		If parent = Null Then Return False
		Return parent.focusedChild = Self
	End
	
	' Visible is read/write
	Method Visible:Bool() Property
		Return visible
	End
	Method Visible:Void(visible:Bool) Property
		Self.visible = visible
	End
	
	' Enabled is read/write
	Method Enabled:Bool() Property
		Return enabled
	End
	Method Enabled:Void(enabled:Bool) Property
		Self.enabled = enabled
	End
	
	' PreferredWidth is read/write
	Method PreferredWidth:Int() Property
		Return preferredWidth
	End
	Method PreferredWidth:Void(preferredWidth:Int) Property
		Self.preferredWidth = preferredWidth
	End
	
	' PreferredHeight is read/write
	Method PreferredHeight:Int() Property
		Return preferredHeight
	End
	Method PreferredHeight:Void(preferredHeight:Int) Property
		Self.preferredHeight = preferredHeight
	End
	
	' MinimumWidth is read/write
	Method MinimumWidth:Int() Property
		Return minimumWidth
	End
	Method MinimumWidth:Void(minimumWidth:Int) Property
		Self.minimumWidth = minimumWidth
	End
	
	' MinimumHeight is read/write
	Method MinimumHeight:Int() Property
		Return minimumHeight
	End
	Method MinimumHeight:Void(minimumHeight:Int) Property
		Self.minimumHeight = minimumHeight
	End
	
	' Alpha is read/write
	Method Alpha:Float() Property
		Return alpha
	End
	Method Alpha:Void(alpha:Float) Property
		Self.alpha = alpha
		If Self.alpha < 0 Then Self.alpha = 0
		If Self.alpha > 1 Then Self.alpha = 1
	End
	
	' ZOrderLocked is read/write, but should only really be used by subclasses!
	Method ZOrderLocked:Bool() Property
		Return zOrderLocked
	End
	Method ZOrderLocked:Void(zOrderLocked:Bool) Property
		Self.zOrderLocked = zOrderLocked
	End
	
	' AbsoluteX is read only because it is calculated
	Method AbsoluteX:Int() Property
		Local rv:Int = x
		Local comp:Component = Self
		While comp.parent <> Null
			comp = comp.parent
			rv += comp.x
		Wend
		Return rv
	End
	
	' AbsoluteY is read only because it is calculated
	Method AbsoluteY:Int() Property
		Local rv:Int = y
		Local comp:Component = Self
		While comp.parent <> Null
			comp = comp.parent
			rv += comp.y
		Wend
		Return rv
	End
	
' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component)
		styleNormal = New ComponentStyle
		' if this isn't the desktop, it must have a parent
		If Not GUIDesktop(Self) Then
			If parent = Null Then NoParent()
			Self.parent = parent
			parent.children.Push(Self)
			parent.childrenZOrder.Push(Self)
			' if the parent has a layout manager, do it!
			If parent.layoutManager <> Null Then parent.Layout()
		Else
			styleNormal.drawBackground = False
		End
		AddNotify()
	End
	
' Public methods
	' Get an associated style based on a name
	Method GetStyle:ComponentStyle(name:String)
		If name = "normal" Then Return styleNormal
		Return Null
	End
	
	' Set an associated style based on a name
	Method SetStyle:Void(name:String, style:ComponentStyle)
		If name = "normal" Then styleNormal = style
	End
	
	' Convenience method to set the style's background properties.  If the component is skinned, the background will be drawn under it, showing through transparency.
	Method SetBackground:Void(red:Int, green:Int, blue:Int)
		If red < 0 Then red = 0
		If red > 255 Then red = 255
		If green < 0 Then green = 0
		If green > 255 Then green = 255
		If blue < 0 Then blue = 0
		If blue > 255 Then blue = 255
		styleNormal.red = red
		styleNormal.green = green
		styleNormal.blue = blue
		styleNormal.drawBackground = True
	End
	
	' Convenience method to enable or disable the background, using whichever colour is already assigned.
	Method SetBackground:Void(drawBackground:Bool)
		styleNormal.drawBackground = drawBackground
	End
	
	' Convenience method to update both the x and y coordinates at once.
	Method SetLocation:Void(x:Int, y:Int)
		Self.x = x
		Self.y = y
	End
	
	' Convenience method to update both the width and height at once. This also fires off the layout manager.
	Method SetSize:Void(w:Int, h:Int)
		Self.w = w
		Self.h = h
		Self.w2 = w / 2.0
		Self.h2 = h / 2.0
		Layout()
	End
	
	' Convenience method to update x, y, width, and height at once. This also fires off the layout manager.
	Method SetBounds:Void(x:Int, y:Int, w:Int, h:Int)
		Self.x = x
		Self.y = y
		Self.w = w
		Self.h = h
		Self.w2 = w / 2.0
		Self.h2 = h / 2.0
		Layout()
	End
	
	' Convenience method to update x, y, width, and height at once using a Rectangle. This also fires off the layout manager.
	Method SetBounds:Void(rect:Rectangle)
		Self.x = rect.x
		Self.y = rect.y
		Self.w = rect.w
		Self.h = rect.h
		Self.w2 = w / 2.0
		Self.h2 = h / 2.0
		Layout()
	End
	
	' Convenience method to set the preferred width and height at once.
	Method SetPreferredSize:Void(w:Int, h:Int)
		Self.preferredWidth = w
		Self.preferredHeight = h
	End
	
	' Convenience method to set the minimum width and height at once.
	Method SetMinimumSize:Void(w:Int, h:Int)
		Self.minimumWidth = w
		Self.minimumHeight = h
	End
	
	' Kicks off the drawing process for this component.
	Method Draw:Void(parentGui:GUI, alpha:Float = 1, absx:Int = 0, absy:Int = 0)
		If visible Then
			parentGui.PushScissor(absx, absy, w, h)
			If Not parentGui.EmptyScissor() Then
				SetAlpha(alpha)
				DrawComponent()
				DrawChildren(parentGui, alpha, absx, absy)
			End
			parentGui.PopScissor()
		End
	End
	
	' Loops through each of the component's children, translating to the correct position and firing off their Draw methods.
	Method DrawChildren:Void(parentGui:GUI, alpha:Float = 1, absx:Int, absy:Int)
		For Local c:Component = EachIn childrenZOrder
			PushMatrix()
			Translate(c.x, c.y)
			c.Draw(parentGui, c.alpha * alpha, absx + c.x, absy + c.y)
			PopMatrix()
		Next
	End

	' Draws the contents of this component (ignoring any children).
	Method DrawComponent:Void()
		' get the gui and the atlas
		Local parentGui:GUI = FindGUI()
		Local atlas:GameImage = parentGui.SkinAtlas
		
		' work with the current style
		Local style:ComponentStyle = GetCurrentStyle()
		If style <> Null Then
			' background colour first
			If style.drawBackground Then
				SetColor(style.red, style.green, style.blue)
				SetAlpha(alpha)
				DrawRect(0, 0, w, h)
			End
			
			' image next, priority = disabled, down, hover, normal
			Local imageType:Int = -1
			If Not enabled Then
				If style.imageMode[ComponentStyle.IMAGE_DISABLED] <> "" Then
					imageType = ComponentStyle.IMAGE_DISABLED
				ElseIf style.imageMode[ComponentStyle.IMAGE_NORMAL] <> "" Then
					imageType = ComponentStyle.IMAGE_NORMAL
				End
			ElseIf mouseDown And style.imageMode[ComponentStyle.IMAGE_DOWN] <> "" Then
				imageType = ComponentStyle.IMAGE_DOWN
			ElseIf mouseHover And style.imageMode[ComponentStyle.IMAGE_HOVER] <> "" Then
				imageType = ComponentStyle.IMAGE_HOVER
			ElseIf style.imageMode[ComponentStyle.IMAGE_NORMAL] <> "" Then
				imageType = ComponentStyle.IMAGE_NORMAL
			End
			
			' if we have an image to draw, do it!
			If imageType >= 0 Then
				SetColor(255,255,255)
				SetAlpha(alpha)
				If style.imageMode[imageType] = ComponentStyle.IMAGE_MODE_GRID Then
					' nasty call here!
					atlas.DrawSubGrid(0, 0, w, h,
							style.imageX[imageType], style.imageY[imageType], style.imageWidth[imageType], style.imageHeight[imageType],
							style.imageLeftMargin[imageType], style.imageRightMargin[imageType], style.imageTopMargin[imageType], style.imageBottomMargin[imageType],
							style.imageDrawTopLeft[imageType], style.imageDrawTop[imageType], style.imageDrawTopRight[imageType],
							style.imageDrawLeft[imageType], style.imageDrawCenter[imageType], style.imageDrawRight[imageType],
							style.imageDrawBottomLeft[imageType], style.imageDrawBottom[imageType], style.imageDrawBottomRight[imageType])
				ElseIf style.imageMode[imageType] = ComponentStyle.IMAGE_MODE_STRETCH Then
					atlas.DrawSubStretched(0, 0, w, h, style.imageX[imageType], style.imageY[imageType], style.imageWidth[imageType], style.imageHeight[imageType])
				ElseIf style.imageMode[imageType] = ComponentStyle.IMAGE_MODE_NORMAL Then
					atlas.DrawSubImage(0, 0, style.imageX[imageType], style.imageY[imageType], style.imageWidth[imageType], style.imageHeight[imageType])
				End
			End
			
			' reset stuff if we changed it
			If style.drawBackground Or imageType >= 0 Then
				SetColor(255,255,255)
				SetAlpha(1)
			End
		End
	End
	
	' Returns whichever style is marked as "current".  Not all styles can be used this way.
	Method GetCurrentStyle:ComponentStyle()
		Return styleNormal
	End
	
	' Frees any resources used by this component, and propagates to its children.
	Method Dispose:Void(recursing:Bool = False)
		' we do an empty check first to save creating an enumerator object
		If Not children.IsEmpty() Then
			#Rem FIXME
			Local enum:IEnumerator<Component> = children.Enumerator()
			While enum.HasNext()
				Local c:Component = enum.NextObject()
				c.Dispose(True)
				childrenZOrder.RemoveItem(c)
				enum.Remove()
			End
			#End
		End
		DisposeNotify()
		Local p:Component = Self.parent
		If p.focusedChild = Self Then p.focusedChild = Null
		Local thisGui:GUI = FindGUI()
		If thisGui.CurrentFocus = Self Then thisGui.CurrentFocus = Null
		Self.parent = Null
		If Not recursing Then
			p.children.RemoveItem(Self)
			p.childrenZOrder.RemoveItem(Self)
		End
	End
	
	' Called when a component is first added to its parent.
	Method AddNotify:Void()
	End
	
	' Called when a component is removed from its parent via Dispose().
	Method DisposeNotify:Void()
	End
	
	' Fires off any assigned layout manager.  May be overridden to do a custom layout without using a manager.
	Method Layout:Void()
		If layoutManager <> Null Then
			Local gui:GUI = FindGUI()
			If gui = Null Or gui.LayoutEnabled Then layoutManager.Layout(Self)
		End
	End
	
	'FIXME: Shouldn't need Object casts for interfaces
	Method FindActionTarget:IActionListener()
		' traverse up the hierarchy and find the closest ActionListener to receive the actions
		Local comp:Component = Self
		While comp <> Null And IActionListener(Object(comp)) = Null
			comp = comp.parent
		End
		If comp <> Null Then Return IActionListener(Object(comp))
		' nfi why comp would be null here with android
		Local gui:GUI = FindGUI()
		Return gui
	End
	
	' Tries to make this component the focused one.
	' If it is focusable, make that the new focus.
	' If it is NOT focusable, we check the last focused child to see if that is focusable. If it is, make it the new focus.
	' If the last focused child isn't focusable, we call RequestFocus on that
	' If RequestFocus failed, we set the focus to the first focusable child
	' If no focusable children, we call RequestFocus on all children in order until we succeed (skipping the last focused child)
	' If those requests failed, we set the focus to the first focusable parent.
	' Returns true if this component or a child received the focus.
	Method RequestFocus:Bool()
		Local thisGui:GUI = FindGUI()
		' if this component is focusable...
		If focusable Then
			thisGui.SetFocus(Self)
			Return True
		End
		' otherwise, call the delegate
		If RequestFocusDelegate(thisGui) Then Return True
		' if we couldn't focus, go through the parent hierarchy
		Local comp:Component = Self.parent
		While comp <> Null And Not comp.focusable
			comp = comp.parent
		End
		' if we hit the top level, we can't focus
		If comp = Null Then Return False
		
		thisGui.SetFocus(comp)
		Return True
	End
	
	' Loops through the parent hierarchy to attempt to find the parentGUI assigned to the desktop.
	Method FindGUI:GUI()
		Local comp:Component = Self
		While comp.parent <> Null
			comp = comp.parent
		End
		If GUIDesktop(comp) <> Null Then Return GUIDesktop(comp).parentGUI
		Return Null
	End
	
	' Moves this component to the top of the z-order, and tells its parent to do the same thing.
	Method BringToFront:Void()
		If parent = Null Then Return
		If parent.childrenZOrder.Count() > 1 And Not zOrderLocked Then
			parent.childrenZOrder.RemoveItem(Self)
			parent.childrenZOrder.Push(Self)
		End
		parent.BringToFront()
	End
	
	' Moves this component to the bottom of the z-order, but leaves its parent alone.
	Method SendToBack:Void()
		If parent = Null Then Return
		If parent.childrenZOrder.Count() > 1 And Not zOrderLocked Then
			parent.childrenZOrder.RemoveItem(Self)
			parent.childrenZOrder.InsertItem(0, Self)
		End
	End
	
	' Should be overridden by each component to provide a matching XML element name in the gui skin xml.
	Method GetSkinNodeName:String()
		Return SKIN_NODE_NONE
	End
	
	' Calculates the minimum amount of space required to display the entire contents of this component.  Used for layout managers.
	Method CalculateMinimum:Point(point:Point=Null, dontCreatePoint:Bool=False)
		If point = Null And Not dontCreatePoint Then point = New Point
		If layoutManager <> Null Then
			point = layoutManager.LayoutMinimum(Self, point)
			minimumWidth = point.x
			minimumHeight = point.y
			Return point
		End
		If point <> Null Then
			point.x = 0
			point.y = 0
		End
		minimumWidth = 0
		minimumHeight = 0
		Return point
	End
	
	Method Pack:Void()
		' if we have no layout manager, just find the bottom-right-most component
		If layoutManager = Null Then
			Local w:Int = 0, h:Int = 0
			For Local i:Int = 0 Until Children.Count()
				Local child:Component = Children.Get(i)
				w = Max(w, child.X + child.Width)
				h = Max(h, child.Y + child.Height)
			Next
			SetSize(w, h)
		Else
			CalculateMinimum(Null,True)
			SetSize(minimumWidth,minimumHeight)
		End
	End
	
	' Fires an ActionPerformed event to the assigned ActionListener.  If no action listener is assigned, it loops through
	' the parent hierarchy until it finds a component that implements IActionListener, or it reaches the desktop.
	Method FireActionPerformed:Void(source:Component, action:String)
		' first do internal
		If internalActionListener <> Null Then internalActionListener.ActionPerformed(Self, action)
		' now user defined
		Local al:IActionListener = actionListener
		If al = Null Then al = FindActionTarget()
		If al <> Null Then al.ActionPerformed(Self, action)
		' if al is null here, something is seriously wrong!!!
	End
	
	Method FireMousePressed:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseListener <> Null Then internalMouseListener.MousePressed(source, x, y, button, absoluteX, absoluteY)
		' now user defined
		If mouseListener <> Null Then mouseListener.MousePressed(source, x, y, button, absoluteX, absoluteY)
	End
	
	Method FireMouseReleased:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseListener <> Null Then internalMouseListener.MouseReleased(source, x, y, button, absoluteX, absoluteY)
		' now user defined
		If mouseListener <> Null Then mouseListener.MouseReleased(source, x, y, button, absoluteX, absoluteY)
	End
	
	Method FireMouseClicked:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseListener <> Null Then internalMouseListener.MouseClicked(source, x, y, button, absoluteX, absoluteY)
		' now user defined
		If mouseListener <> Null Then mouseListener.MouseClicked(source, x, y, button, absoluteX, absoluteY)
	End
	
	Method FireMouseEntered:Void(source:Component, x:Int, y:Int, exitedComp:Component, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseListener <> Null Then internalMouseListener.MouseEntered(source, x, y, exitedComp, absoluteX, absoluteY)
		' now user defined
		If mouseListener <> Null Then mouseListener.MouseEntered(source, x, y, exitedComp, absoluteX, absoluteY)
	End
	
	Method FireMouseExited:Void(source:Component, x:Int, y:Int, enteredComp:Component, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseListener <> Null Then internalMouseListener.MouseExited(source, x, y, enteredComp, absoluteX, absoluteY)
		' now user defined
		If mouseListener <> Null Then mouseListener.MouseExited(source, x, y, enteredComp, absoluteX, absoluteY)
	End
	
	Method FireMouseMoved:Void(source:Component, x:Int, y:Int, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseMotionListener <> Null Then internalMouseMotionListener.MouseMoved(source, x, y, absoluteX, absoluteY)
		' now user defined
		If mouseMotionListener <> Null Then mouseMotionListener.MouseMoved(source, x, y, absoluteX, absoluteY)
	End
	
	Method FireMouseDragged:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		' first do internal
		If internalMouseMotionListener <> Null Then internalMouseMotionListener.MouseDragged(source, x, y, button, absoluteX, absoluteY)
		' now user defined
		If mouseMotionListener <> Null Then mouseMotionListener.MouseDragged(source, x, y, button, absoluteX, absoluteY)
	End
	
	Method FireKeyPressed:Void(source:Component, keychar:String, keycode:Int)
		' first do internal
		If internalKeyListener <> Null Then internalKeyListener.KeyPressed(source, keychar, keycode)
		' now user defined
		If keyListener <> Null Then keyListener.KeyPressed(source, keychar, keycode)
	End
	
	Method FireKeyReleased:Void(source:Component, keychar:String, keycode:Int)
		' first do internal
		If internalKeyListener <> Null Then internalKeyListener.KeyReleased(source, keychar, keycode)
		' now user defined
		If keyListener <> Null Then keyListener.KeyReleased(source, keychar, keycode)
	End
	
	Method FireKeyRepeated:Void(source:Component, keychar:String, keycode:Int)
		' first do internal
		If internalKeyListener <> Null Then internalKeyListener.KeyRepeated(source, keychar, keycode)
		' now user defined
		If keyListener <> Null Then keyListener.KeyRepeated(source, keychar, keycode)
	End
	
	Method FireKeyTyped:Void(source:Component, keychar:String, keycode:Int)
		' first do internal
		If internalKeyListener <> Null Then internalKeyListener.KeyTyped(source, keychar, keycode)
		' now user defined
		If keyListener <> Null Then keyListener.KeyTyped(source, keychar, keycode)
	End
	
	Method FireFocusGained:Void(source:Component, oldFocus:Component)
		' first do internal
		If internalFocusListener <> Null Then internalFocusListener.FocusGained(source, oldFocus)
		' now user defined
		If focusListener <> Null Then focusListener.FocusGained(source, oldFocus)
	End
	
	Method FireFocusLost:Void(source:Component, newFocus:Component)
		' first do internal
		If internalFocusListener <> Null Then internalFocusListener.FocusLost(source, newFocus)
		' now user defined
		If focusListener <> Null Then focusListener.FocusLost(source, newFocus)
	End
End ' Class Component

Class ComponentStyle
Public
' Constants
	Const IMAGE_NORMAL:Int = 0
	Const IMAGE_HOVER:Int = 1
	Const IMAGE_DOWN:Int = 2
	Const IMAGE_DISABLED:Int = 3
	Const IMAGE_FOCUS:Int = 4
	Const IMAGE_COUNT:Int = 5

	Const IMAGE_MODE_NORMAL:String = "normal"
	Const IMAGE_MODE_TILE:String = "tile"
	Const IMAGE_MODE_STRETCH:String = "stretch"
	Const IMAGE_MODE_GRID:String = "grid"

' Public fields
	Field drawBackground:Bool = True
	Field red:Int = 255
	Field green:Int = 255
	Field blue:Int = 255
	
	Field imageMode:String[IMAGE_COUNT]
	Field imageX:Int[IMAGE_COUNT]
	Field imageY:Int[IMAGE_COUNT]
	Field imageWidth:Int[IMAGE_COUNT]
	Field imageHeight:Int[IMAGE_COUNT]
	Field imageLeftMargin:Int[IMAGE_COUNT]
	Field imageRightMargin:Int[IMAGE_COUNT]
	Field imageTopMargin:Int[IMAGE_COUNT]
	Field imageBottomMargin:Int[IMAGE_COUNT]
	Field imageDrawTopLeft:Bool[IMAGE_COUNT]
	Field imageDrawTop:Bool[IMAGE_COUNT]
	Field imageDrawTopRight:Bool[IMAGE_COUNT]
	Field imageDrawLeft:Bool[IMAGE_COUNT]
	Field imageDrawCenter:Bool[IMAGE_COUNT]
	Field imageDrawRight:Bool[IMAGE_COUNT]
	Field imageDrawBottomLeft:Bool[IMAGE_COUNT]
	Field imageDrawBottom:Bool[IMAGE_COUNT]
	Field imageDrawBottomRight:Bool[IMAGE_COUNT]

	Field upSound:String = ""
	Field downSound:String = ""
	Field clickSound:String = ""
	Field enterSound:String = ""
	Field exitSound:String = ""
	
' Constructors
	Method New()
		For Local i% = 0 Until IMAGE_COUNT
			imageMode[i] = ""
		Next
	End

' Public methods
	Method ReadFromNode:Void(node:XMLElement)
		drawBackground = node.GetAttribute("drawBackground", "false") = "true"
		red = Int(node.GetAttribute("red", "255"))
		green = Int(node.GetAttribute("green", "255"))
		blue = Int(node.GetAttribute("blue", "255"))
		For Local subNode:XMLElement = EachIn node.Children
			Local imageType:Int = -1
			If subNode.Name = "enterSound" Then
				enterSound = subNode.GetAttribute("name")
			ElseIf subNode.Name = "exitSound" Then
				exitSound = subNode.GetAttribute("name")
			ElseIf subNode.Name = "downSound" Then
				downSound = subNode.GetAttribute("name")
			ElseIf subNode.Name = "upSound" Then
				upSound = subNode.GetAttribute("name")
			ElseIf subNode.Name = "clickSound" Then
				clickSound = subNode.GetAttribute("name")
			ElseIf subNode.Name = "normalImage" Then
				imageType = IMAGE_NORMAL
			ElseIf subNode.Name = "hoverImage" Then
				imageType = IMAGE_HOVER
			ElseIf subNode.Name = "downImage" Then
				imageType = IMAGE_DOWN
			ElseIf subNode.Name = "disabledImage" Then
				imageType = IMAGE_DISABLED
			ElseIf subNode.Name = "focusImage" Then
				imageType = IMAGE_FOCUS
			End
			
			If imageType >= 0 Then
				imageMode[imageType]            = subNode.GetAttribute("mode")
				imageX[imageType]               = Int(subNode.GetAttribute("x", "0"))
				imageY[imageType]               = Int(subNode.GetAttribute("y", "0"))
				imageWidth[imageType]           = Int(subNode.GetAttribute("width", "0"))
				imageHeight[imageType]          = Int(subNode.GetAttribute("height", "0"))
				imageLeftMargin[imageType]      = Int(subNode.GetAttribute("leftMargin", "0"))
				imageRightMargin[imageType]     = Int(subNode.GetAttribute("rightMargin", "0"))
				imageTopMargin[imageType]       = Int(subNode.GetAttribute("topMargin", "0"))
				imageBottomMargin[imageType]    = Int(subNode.GetAttribute("bottomMargin", "0"))
				imageDrawTopLeft[imageType]     = subNode.GetAttribute("drawTopLeft", "true") = "true"
				imageDrawTop[imageType]         = subNode.GetAttribute("drawTop", "true") = "true"
				imageDrawTopRight[imageType]    = subNode.GetAttribute("drawTopRight", "true") = "true"
				imageDrawLeft[imageType]        = subNode.GetAttribute("drawLeft", "true") = "true"
				imageDrawCenter[imageType]      = subNode.GetAttribute("drawCenter", "true") = "true"
				imageDrawRight[imageType]       = subNode.GetAttribute("drawRight", "true") = "true"
				imageDrawBottomLeft[imageType]  = subNode.GetAttribute("drawBottomLeft", "true") = "true"
				imageDrawBottom[imageType]      = subNode.GetAttribute("drawBottom", "true") = "true"
				imageDrawBottomRight[imageType] = subNode.GetAttribute("drawBottomRight", "true") = "true"
			End
		Next
	End
End ' Class ComponentStyle

Class GUIDesktop Extends Component
Private
' Private fields
	Field restrictWindows:Bool = True
	Field parentGUI:GUI
	
Public
' Properties
	' RestrictWindows is read/write
	Method RestrictWindows:Bool() Property
		Return restrictWindows
	End
	Method RestrictWindows:Void(restrictWindows:Bool) Property
		Self.restrictWindows = restrictWindows
	End
	
	' ParentGUI is read only
	Method ParentGUI:GUI() Property
		Return parentGUI
	End
	
' Constructors
	Method New()
		AssertError("Must pass a parent GUI.")
	End
	
	Method New(parentGUI:GUI)
		Super.New(Null)
		Self.parentGUI = parentGUI
	End
End ' Class GUIDesktop

Class Panel Extends Component
Private
' Private methods
	Method ApplySkin:Void()
		Super.ApplySkin()
		For Local i:Int = 0 Until children.Count()
			children.Get(i).ApplySkin()
		Next
	End

Public
' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component)
		Super.New(parent)
	End
End ' Class Panel

Class Window Extends Component
Private
' Private fields
	Field contentPane:Panel
	Field titlePane:Label
	Field buttonPane:Panel
	
	Field styleShadeButton:ComponentStyle
	Field styleMinimizeButton:ComponentStyle
	Field styleMaximizeButton:ComponentStyle
	Field styleRestoreButton:ComponentStyle
	Field styleCloseButton:ComponentStyle
	Field styleContentPane:ComponentStyle
	Field styleButtonPane:ComponentStyle
	Field styleTitlePane:ComponentStyle
	
	Field closeButton:Button
	Field maximizeButton:Button
	Field minimizeButton:Button
	Field shadeButton:Button
	Field internalWindowAdapter:InternalWindowAdapter
	
	' read from skin
	Field contentPaneLeft:Int
	Field contentPaneRight:Int
	Field contentPaneTop:Int
	Field contentPaneBottom:Int
	Field titlePaneLeft:Int
	Field titlePaneRight:Int
	Field titlePaneTop:Int
	Field titlePaneBottom:Int
	Field buttonPaneRight:Int
	Field buttonPaneTop:Int
	Field buttonPaneBottom:Int
	Field buttonWidth:Int
	Field buttonHeight:Int
	Field buttonMargin:Int
	Field titlePaneTextXOffset:Int
	Field titlePaneTextYOffset:Int
	
	' note: a window can be all three of these states at once!
	' priority is: minimized, maximized, shaded
	Field maximized:Bool = False
	Field minimized:Bool = False
	Field shaded:Bool = False
	Field dragX:Int, dragY:Int, originalX:Int, originalY:Int
	Field dragging:Bool = False
	
	Field normalX:Int, normalY:Int, normalWidth:Int, normalHeight:Int

	Field showMinimize:Bool = False
	Field showMaximize:Bool = False
	Field showShade:Bool = False
	Field showClose:Bool = True
	
	Field title:String = ""
	
' Private methods
	Method CreateButtonPane:Void()
		buttonPane = New Panel(Self)
		shadeButton = New Button(buttonPane)
		shadeButton.internalActionListener = internalWindowAdapter
		minimizeButton = New Button(buttonPane)
		minimizeButton.internalActionListener = internalWindowAdapter
		maximizeButton = New Button(buttonPane)
		maximizeButton.toggle = True
		maximizeButton.internalActionListener = internalWindowAdapter
		closeButton = New Button(buttonPane)
		closeButton.internalActionListener = internalWindowAdapter
	End
	
	Method CreateContentPane:Void()
		contentPane = New Panel(Self)
		contentPane.SetBackground(224,224,224)
	End
	
	Method CreateTitlePane:Void()
		titlePane = New Label(Self)
		titlePane.SetBackground(False)
		titlePane.internalMouseListener = internalWindowAdapter
		titlePane.internalMouseMotionListener = internalWindowAdapter
		titlePane.text = title
		titlePane.textRed = 0
		titlePane.textGreen = 0
		titlePane.textBlue = 0
		titlePane.textXOffset = titlePaneTextXOffset
		titlePane.textYOffset = titlePaneTextYOffset
	End
	
	Method StoreWindowSize:Void()
		If maximized Or minimized Then Return
		normalX = x
		normalY = y
		If Not shaded Then
			normalWidth = w
			normalHeight = h
		End
	End
	
	Method UpdateWindowSize:Void()
		If Not maximized And Not minimized And Not shaded Then
			SetBounds(normalX, normalY, normalWidth, normalHeight)
		ElseIf minimized Then
			SetBounds(0, 0, 50, contentPaneTop)
		ElseIf maximized Then
			SetBounds(-contentPaneLeft, -titlePaneTop, parent.w+contentPaneLeft-contentPaneRight, parent.h+titlePaneTop-contentPaneBottom)
		ElseIf shaded Then
			SetBounds(normalX, normalY, normalWidth, contentPaneTop)
		End
	End
	
	Method ReadSkinFields:Void(node:XMLElement)
		Super.ReadSkinFields(node)
		contentPaneLeft   = Int(node.GetAttribute("contentPaneLeft","0"))
		contentPaneRight  = Int(node.GetAttribute("contentPaneRight","-1"))
		contentPaneTop    = Int(node.GetAttribute("contentPaneTop","0"))
		contentPaneBottom = Int(node.GetAttribute("contentPaneBottom","-1"))
		titlePaneLeft     = Int(node.GetAttribute("titlePaneLeft","0"))
		titlePaneRight    = Int(node.GetAttribute("titlePaneRight","-1"))
		titlePaneTop      = Int(node.GetAttribute("titlePaneTop","0"))
		titlePaneBottom   = Int(node.GetAttribute("titlePaneBottom","-1"))
		buttonPaneRight   = Int(node.GetAttribute("buttonPaneRight","-1"))
		buttonPaneTop     = Int(node.GetAttribute("buttonPaneTop","0"))
		buttonPaneBottom  = Int(node.GetAttribute("buttonPaneBottom","-1"))
		buttonWidth       = Int(node.GetAttribute("buttonWidth","17"))
		buttonHeight      = Int(node.GetAttribute("buttonHeight","17"))
		buttonMargin      = Int(node.GetAttribute("buttonMargin","1"))
		titlePaneTextXOffset = Int(node.GetAttribute("titlePaneTextXOffset","0"))
		titlePaneTextYOffset = Int(node.GetAttribute("titlePaneTextYOffset","0"))
	End
	
	Method LoadStyles:Void(node:XMLElement)
		Super.LoadStyles(node)
		If styleShadeButton <> Null Then shadeButton.StyleNormal = styleShadeButton
		If styleMinimizeButton <> Null Then minimizeButton.StyleNormal = styleMinimizeButton
		If styleMaximizeButton <> Null Then maximizeButton.StyleNormal = styleMaximizeButton
		If styleRestoreButton <> Null Then maximizeButton.StyleSelected = styleRestoreButton
		If styleCloseButton <> Null Then closeButton.StyleNormal = styleCloseButton
		If styleContentPane <> Null Then contentPane.StyleNormal = styleContentPane
		If styleTitlePane <> Null Then titlePane.StyleNormal = styleTitlePane
		If styleButtonPane <> Null Then buttonPane.StyleNormal = styleButtonPane
	End
	
Public
' Properties
	' Title is read/write and updates the title pane's text.
	Method Title:String() Property
		Return title
	End
	Method Title:Void(title:String) Property
		Self.title = title
		If titlePane <> Null Then titlePane.text = title
	End
	
	' ContentPane is read/write (but shouldn't really be replaced)
	Method ContentPane:Panel() Property
		Return contentPane
	End
	Method ContentPane:Void(contentPane:Panel) Property
		AssertNotNull(contentPane, "Content pane may not be null.")
		If Self.contentPane <> Null Then Self.contentPane.Dispose()
		Self.contentPane = contentPane
		Self.contentPane.ApplySkin()
	End
	
	' TitlePane is read only
	Method TitlePane:Label() Property
		Return titlePane
	End
	
	' ButtonPane is read only
	Method ButtonPane:Panel() Property
		Return buttonPane
	End
	
	' Maximized is read/write
	Method Maximized:Void(maximized:Bool) Property
		StoreWindowSize()
		Self.maximized = maximized
		UpdateWindowSize()
	End
	Method Maximized:Bool() Property
		Return maximized
	End
	
	' Minimized is read/write
	Method Minimized:Void(minimized:Bool) Property
		StoreWindowSize()
		Self.minimized = minimized
		UpdateWindowSize()
	End
	Method Minimized:Bool() Property
		Return minimized
	End
	
	' Shaded is read/write
	Method Shaded:Void(shaded:Bool) Property
		StoreWindowSize()
		Self.shaded = shaded
		UpdateWindowSize()
	End
	Method Shaded:Bool() Property
		Return shaded
	End
	
	' ShowMinimize is read/write and forces a Layout and ApplySkin
	Method ShowMinimize:Bool() Property
		Return showMinimize
	End
	Method ShowMinimize:Void(showMinimize:Bool) Property
		If Self.showMinimize <> showMinimize Then
			Self.showMinimize = showMinimize
			Layout()
			ApplySkin()
		End
	End
	
	' ShowMaximize is read/write and forces a Layout and ApplySkin
	Method ShowMaximize:Bool() Property
		Return showMaximize
	End
	Method ShowMaximize:Void(showMaximize:Bool) Property
		If Self.showMaximize <> showMaximize Then
			Self.showMaximize = showMaximize
			Layout()
			ApplySkin()
		End
	End
	
	' ShowShade is read/write and forces a Layout and ApplySkin
	Method ShowShade:Bool() Property
		Return showShade
	End
	Method ShowShade:Void(showShade:Bool) Property
		If Self.showShade <> showShade Then
			Self.showShade = showShade
			Layout()
			ApplySkin()
		End
	End
	
	' ShowClose is read/write and forces a Layout and ApplySkin
	Method ShowClose:Bool() Property
		Return showClose
	End
	Method ShowClose:Void(showClose:Bool) Property
		If Self.showClose <> showClose Then
			Self.showClose = showClose
			Layout()
			ApplySkin()
		End
	End
	
' Constructors
	Method New()
		AssertError("Must pass a desktop.")
	End
	
	Method New(parent:GUIDesktop)
		Super.New(parent)
		internalWindowAdapter = New InternalWindowAdapter(Self)
		CreateButtonPane()
		CreateContentPane()
		CreateTitlePane()
		ApplySkin()
		Layout()
	End
	
' Overrides Component
	' Lays out all the panels and buttons.
	Method Layout:Void()
		If contentPane = Null Then Return
		Local l:Int,r:Int,t:Int,b:Int
		If minimized Or shaded Then
			If contentPane <> Null Then contentPane.visible = False
		Else
			If contentPane <> Null Then
				contentPane.visible = True
				l = contentPaneLeft
				r = contentPaneRight
				t = contentPaneTop
				b = contentPaneBottom
				If l < 0 Then l += w
				If r < 0 Then r += w
				If t < 0 Then t += h
				If b < 0 Then b += h
				contentPane.SetBounds(l, t, r-l, b-t)
			End
		End
		Local buttonX:Int = 0
		If showShade Then
			shadeButton.visible = True
			shadeButton.SetBounds(buttonX,0,buttonWidth,buttonHeight)
			buttonX += buttonWidth + buttonMargin
		End
		If showMinimize Then
			minimizeButton.visible = True
			minimizeButton.SetBounds(buttonX,0,buttonWidth,buttonHeight)
			buttonX += buttonWidth + buttonMargin
		End
		If showMaximize Then
			maximizeButton.visible = True
			maximizeButton.SetBounds(buttonX,0,buttonWidth,buttonHeight)
			buttonX += buttonWidth + buttonMargin
		End
		If showClose Then
			closeButton.visible = True
			closeButton.SetBounds(buttonX,0,buttonWidth,buttonHeight)
			buttonX += buttonWidth + buttonMargin
		End
		If buttonX = 0 Then
			buttonPane.visible = False
		Else
			buttonPane.visible = True
			r = buttonPaneRight
			t = buttonPaneTop
			b = buttonPaneBottom
			If r < 0 Then r += w
			If t < 0 Then t += h
			If b < 0 Then b += h
			l = r - buttonX + buttonMargin
			buttonPane.SetBounds(l, t, r-l, b-t)
		End
		l = titlePaneLeft
		r = titlePaneRight
		t = titlePaneTop
		b = titlePaneBottom
		If l < 0 Then l += w
		If t < 0 Then t += h
		If b < 0 Then b += h
		If r < 0 Then
			If buttonPane.visible Then
				r += buttonPane.x
			Else
				r += w
			End
		End
		If titlePane <> Null Then
			titlePane.text = title
			titlePane.textXOffset = titlePaneTextXOffset
			titlePane.textYOffset = titlePaneTextYOffset
			titlePane.SetBounds(l, t, r-l, b-t)
		End
	End
	
	' Provide the node name of "window".
	Method GetSkinNodeName:String()
		Return SKIN_NODE_WINDOW
	End
	
	Method ApplySkin:Void()
		Super.ApplySkin()
		If contentPane <> Null Then contentPane.ApplySkin()
	End
	
	' Handles the custom styles.
	Method SetStyle:Void(name:String, style:ComponentStyle)
		If name = "contentPane" Then
			styleContentPane = style
		ElseIf name = "buttonPane" Then
			styleButtonPane = style
		ElseIf name = "titlePane" Then
			styleTitlePane = style
		ElseIf name = "shadeButton" Then
			styleShadeButton = style
		ElseIf name = "minimizeButton" Then
			styleMinimizeButton = style
		ElseIf name = "maximizeButton" Then
			styleMaximizeButton = style
		ElseIf name = "restoreButton" Then
			styleRestoreButton = style
		ElseIf name = "closeButton" Then
			styleCloseButton = style
		Else
			Super.SetStyle(name, style)
		End
	End
End ' Class Window

Class Label Extends Component
Private
' Private fields
	Field text:String
	Field textRed:Int, textGreen:Int, textBlue:Int
	Field textXOffset:Int = 0
	Field textYOffset:Int = 0
	Field textXAlign:Float = 0
	Field textYAlign:Float = 0
	Field useBaseline:Bool = True

	Field simpleNormalImage:GameImage
	
	Method ReadSkinFields:Void(node:XMLElement)
		Super.ReadSkinFields(node)
		textXOffset = Int(node.GetAttribute("textXOffset", "5"))
		textYOffset = Int(node.GetAttribute("textYOffset", "14"))
		textXAlign = Float(node.GetAttribute("textXAlign", "0"))
		textYAlign = Float(node.GetAttribute("textYAlign", "0"))
		useBaseline = (node.GetAttribute("useBaseline","true") = "true")
	End
	
Public
' Properties
	' Text is read/write
	Method Text:String() Property
		Return text
	End
	Method Text:Void(text:String) Property
		Self.text = text
	End
	
	' SimpleNormalImage is read/write
	Method SimpleNormalImage:GameImage() Property
		Return simpleNormalImage
	End
	Method SimpleNormalImage:Void(simpleNormalImage:GameImage) Property
		Self.simpleNormalImage = simpleNormalImage
	End
	
	' Convenience method to set the text, alignment, and offset all at once.
	Method SetText:Void(text:String, textXAlign:Float=0, textYAlign:Float=0, textXOffset:Int=0, textYOffset:Int=0)
		Self.text = text
		Self.textXAlign = textXAlign
		Self.textYAlign = textYAlign
		Self.textXOffset = textXOffset
		Self.textYOffset = textYOffset
	End
	
' Constructors
	Method New(parent:Component, simpleNormalImage:GameImage=Null)
		Super.New(parent)
		If simpleNormalImage <> Null Then
			CheckMidhandle(simpleNormalImage)
			Self.simpleNormalImage = simpleNormalImage
			Self.SetPreferredSize(simpleNormalImage.w, simpleNormalImage.h)
			Self.SetSize(simpleNormalImage.w, simpleNormalImage.h)
		End
		ApplySkin()
	End

' Overrides Component
	' Overrides Component.DrawComponent() to render the text.
	Method DrawComponent:Void()
		If simpleNormalImage <> Null Then
			simpleNormalImage.Draw(0, 0)
		Else
			Super.DrawComponent()
			If text.Length > 0 And FontName.Length > 0 And Font.fonts.Contains(FontName) Then
				If textXOffset = 0 And textYOffset = 0 Then
					Font.fonts.Get(FontName).DrawString(text, w*textXAlign, h*textYAlign, textXAlign, textYAlign, useBaseline, textRed, textGreen, textBlue)
				Else
					Font.fonts.Get(FontName).DrawString(text, textXOffset, textYOffset, textXAlign, textYAlign, useBaseline, textRed, textGreen, textBlue)
				End
			End
		End
	End
	
	Method GetSkinNodeName:String()
		Return SKIN_NODE_LABEL
	End
End ' Class Label

Class Button Extends Label
Private
' Private fields
	Field styleSelected:ComponentStyle = Null
	Field internalButtonAdapter:InternalButtonAdapter
	Field selected:Bool
	Field toggle:Bool
	
	Field simpleHoverImage:GameImage
	Field simpleDownImage:GameImage
	Field simpleSelectedNormalImage:GameImage
	Field simpleSelectedHoverImage:GameImage
	Field simpleSelectedDownImage:GameImage
	
' Private methods
	Method ReadSkinFields:Void(node:XMLElement)
		Super.ReadSkinFields(node)
		toggle = (node.GetAttribute("toggle","false") = "true")
	End

Public
' Properties
	' StyleSelected is read/write
	Method StyleSelected:ComponentStyle() Property
		If styleSelected = Null Then styleSelected = New ComponentStyle
		Return styleSelected
	End
	Method StyleSelected:Void(style:ComponentStyle) Property
		styleSelected = style
	End
	
	' Selected is read/write
	Method Selected:Bool() Property
		Return selected
	End
	Method Selected:Void(selected:Bool) Property
		Self.selected = selected
	End
	
	' Toggle is read/write
	Method Toggle:Bool() Property
		Return toggle
	End
	Method Toggle:Void(toggle:Bool) Property
		Self.toggle = toggle
	End
	
	' SimpleNormalImage is read/write
	Method SimpleNormalImage:GameImage() Property
		Return simpleNormalImage
	End
	Method SimpleNormalImage:Void(simpleNormalImage:GameImage) Property
		Self.simpleNormalImage = simpleNormalImage
	End
	
	' SimpleHoverImage is read/write
	Method SimpleHoverImage:GameImage() Property
		Return simpleHoverImage
	End
	Method SimpleHoverImage:Void(simpleHoverImage:GameImage) Property
		Self.simpleHoverImage = simpleHoverImage
	End
	
	' SimpleDownImage is read/write
	Method SimpleDownImage:GameImage() Property
		Return simpleDownImage
	End
	Method SimpleDownImage:Void(simpleDownImage:GameImage) Property
		Self.simpleDownImage = simpleDownImage
	End
	
	' SimpleSelectedNormalImage is read/write
	Method SimpleSelectedNormalImage:GameImage() Property
		Return simpleSelectedNormalImage
	End
	Method SimpleSelectedNormalImage:Void(simpleSelectedNormalImage:GameImage) Property
		Self.simpleSelectedNormalImage = simpleSelectedNormalImage
	End
	
	' SimpleSelectedHoverImage is read/write
	Method SimpleSelectedHoverImage:GameImage() Property
		Return simpleSelectedHoverImage
	End
	Method SimpleSelectedHoverImage:Void(simpleSelectedHoverImage:GameImage) Property
		Self.simpleSelectedHoverImage = simpleSelectedHoverImage
	End
	
	' SimpleSelectedDownImage is read/write
	Method SimpleSelectedDownImage:GameImage() Property
		Return simpleSelectedDownImage
	End
	Method SimpleSelectedDownImage:Void(simpleSelectedDownImage:GameImage) Property
		Self.simpleSelectedDownImage = simpleSelectedDownImage
	End
	
' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component, simpleNormalImage:GameImage=Null, simpleHoverImage:GameImage=Null, simpleDownImage:GameImage=Null)
		Super.New(parent)
		focusable = True
		internalButtonAdapter = New InternalButtonAdapter(Self)
		internalMouseListener = internalButtonAdapter
		If simpleDownImage <> Null Then
			CheckMidhandle(simpleDownImage)
			Self.simpleDownImage = simpleDownImage
		End
		If simpleHoverImage <> Null Then
			CheckMidhandle(simpleHoverImage)
			Self.simpleHoverImage = simpleHoverImage
		End
		If simpleNormalImage <> Null Then
			CheckMidhandle(simpleNormalImage)
			Self.simpleNormalImage = simpleNormalImage
			Self.SetPreferredSize(simpleNormalImage.w, simpleNormalImage.h)
			Self.SetSize(simpleNormalImage.w, simpleNormalImage.h)
		End
		ApplySkin()
	End
	
' Overrides Component
	Method DrawComponent:Void()
		Local simple:GameImage = Null
		If selected Then
			If mouseDown And simpleSelectedDownImage <> Null Then
				simple = simpleSelectedDownImage
			ElseIf mouseHover And simpleSelectedHoverImage <> Null Then
				simple = simpleSelectedHoverImage
			ElseIf simpleSelectedNormalImage <> Null Then
				simple = simpleSelectedNormalImage
			End
		Else
			If mouseDown And simpleDownImage <> Null Then
				simple = simpleDownImage
			ElseIf mouseHover And simpleHoverImage <> Null Then
				simple = simpleHoverImage
			End
		End
		If simple <> Null Then
			simple.Draw(0, 0)
		Else
			Super.DrawComponent()
		End
	End
	
	Method GetStyle:ComponentStyle(name:String)
		If name = "selected" Then Return styleSelected
		Return Super.GetStyle(name)
	End
	
	Method SetStyle:Void(name:String, style:ComponentStyle)
		If name = "selected" Then
			styleSelected = style
		Else
			Super.SetStyle(name, style)
		End
	End
	
	Method GetCurrentStyle:ComponentStyle()
		If selected And styleSelected <> Null Then Return styleSelected
		Return Super.GetCurrentStyle()
	End
	
	Method GetSkinNodeName:String()
		Return SKIN_NODE_BUTTON
	End
End ' Class Button

Class RadioButton Extends Button
Private
' Private fields
	Field radioGroup:RadioGroup
	Field radioValue:String

Public
' Properties
	' AssignedRadioGroup is read only (remove the RadioButton through the group itself)
	Method AssignedRadioGroup:RadioGroup() Property
		Return radioGroup
	End
	
	' RadioValue is read/write
	Method RadioValue:String() Property
		Return radioValue
	End
	Method RadioValue:Void(radioValue:String) Property
		If radioGroup <> Null And radioGroup.currentValue = Self.radioValue Then
			radioGroup.currentValue = radioValue
		End
		Self.radioValue = radioValue
	End
	
' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component, simpleNormalImage:GameImage=Null, simpleHoverImage:GameImage=Null, simpleDownImage:GameImage=Null,
			simpleSelectedNormalImage:GameImage=Null, simpleSelectedHoverImage:GameImage=Null, simpleSelectedDownImage:GameImage=Null)
		Super.New(parent, simpleNormalImage, simpleHoverImage, simpleDownImage)
		If simpleSelectedNormalImage <> Null Then
			Self.simpleSelectedNormalImage = simpleSelectedNormalImage
			CheckMidhandle(simpleSelectedNormalImage)
		End
		If simpleSelectedHoverImage <> Null Then
			Self.simpleSelectedHoverImage = simpleSelectedHoverImage
			CheckMidhandle(simpleSelectedHoverImage)
		End
		If simpleSelectedDownImage <> Null Then
			Self.simpleSelectedDownImage = simpleSelectedDownImage
			CheckMidhandle(simpleSelectedDownImage)
		End
	End

' Overrides Component
	' Overrides Component.GetSkinNodeName() to return the node name of "radio".	
	Method GetSkinNodeName:String()
		Return SKIN_NODE_RADIO
	End
End ' Class RadioButton

Class Checkbox Extends Button
Public
' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component, simpleNormalImage:GameImage=Null, simpleHoverImage:GameImage=Null, simpleDownImage:GameImage=Null,
			simpleSelectedNormalImage:GameImage=Null, simpleSelectedHoverImage:GameImage=Null, simpleSelectedDownImage:GameImage=Null)
		Super.New(parent, simpleNormalImage, simpleHoverImage, simpleDownImage)
		If simpleSelectedNormalImage <> Null Then
			Self.simpleSelectedNormalImage = simpleSelectedNormalImage
			CheckMidhandle(simpleSelectedNormalImage)
		End
		If simpleSelectedHoverImage <> Null Then
			Self.simpleSelectedHoverImage = simpleSelectedHoverImage
			CheckMidhandle(simpleSelectedHoverImage)
		End
		If simpleSelectedDownImage <> Null Then
			Self.simpleSelectedDownImage = simpleSelectedDownImage
			CheckMidhandle(simpleSelectedDownImage)
		End
	End
	
' Overrides Component
	' Overrides Component.GetSkinNodeName() to return the node name of "checkbox".	
	Method GetSkinNodeName:String()
		Return SKIN_NODE_CHECKBOX
	End
End ' Class Checkbox

Class RadioGroup
Private
' Private fields
	Field buttons:DiddyStack<RadioButton> = New DiddyStack<RadioButton>
	Field currentValue:String
	
Public
' Properties
	' RadioButtons is read only
	Method RadioButtons:DiddyStack<RadioButton>() Property
		Return buttons
	End
	
	' SelectedButton is read/write
	Method SelectedButton:RadioButton() Property
		For Local b:RadioButton = EachIn buttons
			If b.radioValue = currentValue Then Return b
		Next
		Return Null
	End
	Method SelectedButton:Void(button:RadioButton) Property
		Local oldValue:String = currentValue
		Local oldButton:Button = Null
		For Local b:RadioButton = EachIn buttons
			If b.selected Then oldButton = b
			b.selected = (b = button)
			If b.selected Then currentValue = b.radioValue
		Next
		If oldButton <> button Then ValueChanged(currentValue, RadioButton(button), oldValue, RadioButton(oldButton))
	End
	
	' SelectedValue is read/write
	Method SelectedValue:String() Property
		Return currentValue
	End
	Method SelectedValue:Void(value:String) Property
		Local oldValue:String = currentValue
		Local oldButton:Button = Null
		Local newButton:Button = Null
		For Local b:RadioButton = EachIn buttons
			If b.selected Then oldButton = b
			b.selected = (b.radioValue = value)
			If b.selected Then
				currentValue = value
				newButton = b
			End
		Next
		If oldValue <> value Then ValueChanged(currentValue, RadioButton(newButton), oldValue, RadioButton(oldButton))
	End
	
' Public methods
	Method AddButton:Void(button:RadioButton, value:String)
		button.radioValue = value
		button.radioGroup = Self
		buttons.Push(button)
	End
	
	Method RemoveButton:Void(button:RadioButton)
		button.radioValue = ""
		button.radioGroup = Null
		buttons.RemoveItem(button)
	End
	
	Method ValueChanged:Void(newValue:String, newButton:RadioButton, oldValue:String, oldButton:RadioButton)
	End
End

Class Slider Extends Component
Public
' Constants
	Const SLIDER_HORIZONTAL:Int = 0
	Const SLIDER_VERTICAL:Int = 1
	Const SLIDER_DIRECTION_TL_TO_BR:Int = 0 ' min is top or left, max is bottom or right
	Const SLIDER_DIRECTION_BR_TO_TL:Int = 1 ' min is bottom or right, max is top or left
	
Private
' Private fields
	Field buttonUpLeft:Button ' the button used for up and left
	Field buttonDownRight:Button ' the button used for down and right
	Field handle:Label
	Field bar:Label
	Field showButtons:Bool
	Field orientation:Int = SLIDER_HORIZONTAL
	Field direction:Int = SLIDER_DIRECTION_TL_TO_BR
	
	Field dragX:Int, dragY:Int, originalX:Int, originalY:Int
	Field dragging:Bool = False
	
	Field styleLeftButton:ComponentStyle = New ComponentStyle
	Field styleRightButton:ComponentStyle = New ComponentStyle
	Field styleTopButton:ComponentStyle = New ComponentStyle
	Field styleBottomButton:ComponentStyle = New ComponentStyle
	Field styleHorizontalHandle:ComponentStyle = New ComponentStyle
	Field styleVerticalHandle:ComponentStyle = New ComponentStyle
	Field styleHorizontalBar:ComponentStyle = New ComponentStyle
	Field styleVerticalBar:ComponentStyle = New ComponentStyle
	
	Field internalSliderAdapter:InternalSliderAdapter
	
	Field leftButtonWidth:Int
	Field leftButtonHeight:Int
	Field rightButtonWidth:Int
	Field rightButtonHeight:Int
	Field topButtonWidth:Int
	Field topButtonHeight:Int
	Field bottomButtonWidth:Int
	Field bottomButtonHeight:Int
	
	Field minValue:Int = 0
	Field maxValue:Int = 100
	Field value:Int = 50
	Field tickInterval:Int = 10
	Field handleMargin:Int = 10
	Field handleSize:Int = 10
	Field buttonSize:Int = 15
	Field snapToTicks:Bool = True

' Private methods
	Method DoDrag:Void(mx:Int, my:Int)
		Local pos:Int, topLeft:Int = handleMargin, bottomRight:Int = -handleMargin
		If showButtons Then
			topLeft += buttonSize
			bottomRight -= buttonSize
		End
		If orientation = SLIDER_HORIZONTAL Then
			bottomRight += w
			pos = Min(Max(topLeft, mx), bottomRight)
		Else
			bottomRight += h
			pos = Min(Max(topLeft, my), bottomRight)
		End
		Local fraction:Float = Float(pos-topLeft) / Float(bottomRight-topLeft)
		If direction = SLIDER_DIRECTION_BR_TO_TL Then fraction = 1-fraction
		
		Local oldValue:Int = value
		value = SnapToValue(minValue + (maxValue - minValue)*fraction)
		
		' if it changed, update the layout and fire an event
		If value <> oldValue Then
			Layout()
			FireActionPerformed(Self, ACTION_VALUE_CHANGED)
		End
	End
	
	Method UpdateStyles:Void()
		If orientation = SLIDER_HORIZONTAL Then
			buttonUpLeft.StyleNormal = styleLeftButton
			buttonDownRight.StyleNormal = styleRightButton
			bar.StyleNormal = styleHorizontalBar
			handle.StyleNormal = styleHorizontalHandle
		Else
			buttonUpLeft.StyleNormal = styleTopButton
			buttonDownRight.StyleNormal = styleBottomButton
			bar.StyleNormal = styleVerticalBar
			handle.StyleNormal = styleVerticalHandle
		End
	End
	
	Method ReadSkinFields:Void(node:XMLElement)
		handleMargin = Int(node.GetAttribute("handleMargin", "8"))
		leftButtonWidth = Int(node.GetAttribute("leftButtonWidth", "15"))
		leftButtonHeight = Int(node.GetAttribute("leftButtonHeight", "16"))
		rightButtonWidth = Int(node.GetAttribute("rightButtonWidth", "15"))
		rightButtonHeight = Int(node.GetAttribute("rightButtonHeight", "16"))
		topButtonWidth = Int(node.GetAttribute("topButtonWidth", "16"))
		topButtonHeight = Int(node.GetAttribute("topButtonHeight", "15"))
		bottomButtonWidth = Int(node.GetAttribute("bottomButtonWidth", "16"))
		bottomButtonHeight = Int(node.GetAttribute("bottomButtonHeight", "15"))
	End
	
Public
' Properties
	' ShowButtons is read/write and fires off a Layout()
	Method ShowButtons:Bool() Property
		Return showButtons
	End
	Method ShowButtons:Void(showButtons:Bool) Property
		If showButtons <> Self.showButtons Then
			Self.showButtons = showButtons
			Layout()
		End
		Self.showButtons = showButtons
	End
	
	' Orientation is read/write and fires off a Layout()
	Method Orientation:Int() Property
		Return orientation
	End
	Method Orientation:Void(orientation:Int) Property
		If Self.orientation <> orientation Then
			Self.orientation = orientation
			UpdateStyles()
			Layout()
		End
		Self.orientation = orientation
	End
	
	' Direction is read/write and fires off a Layout()
	Method Direction:Int() Property
		Return direction
	End
	Method Direction:Void(direction:Int) Property
		If Self.direction <> direction Then
			Self.direction = direction
			Layout()
		End
		Self.direction = direction
	End
	
	' MinValue is read/write
	Method MinValue:Int() Property
		Return minValue
	End
	Method MinValue:Void(minValue:Int) Property
		Self.minValue = minValue
	End
	
	' MaxValue is read/write
	Method MaxValue:Int() Property
		Return maxValue
	End
	Method MaxValue:Void(maxValue:Int) Property
		Self.maxValue = maxValue
	End
	
	' Value is read/write
	Method Value:Int() Property
		Return value
	End
	Method Value:Void(value:Int) Property
		Self.value = value
	End
	
	' TickInterval is read/write
	Method TickInterval:Int() Property
		Return tickInterval
	End
	Method TickInterval:Void(tickInterval:Int) Property
		Self.tickInterval = tickInterval
	End
	
	' SnapToTicks is read/write
	Method SnapToTicks:Bool() Property
		Return snapToTicks
	End
	Method SnapToTicks:Void(snapToTicks:Bool) Property
		Self.snapToTicks = snapToTicks
	End

' Constructors
	Method New()
		NoParent()
	End
	
	Method New(parent:Component)
		Super.New(parent)
		internalSliderAdapter = New InternalSliderAdapter(Self)
		
		bar = New Label(Self)
		bar.zOrderLocked = True
		bar.internalMouseListener = internalSliderAdapter
		bar.internalMouseMotionListener = internalSliderAdapter
		
		buttonUpLeft = New Button(Self)
		buttonUpLeft.internalActionListener = internalSliderAdapter
		buttonUpLeft.zOrderLocked = True
		
		buttonDownRight = New Button(Self)
		buttonDownRight.internalActionListener = internalSliderAdapter
		buttonDownRight.zOrderLocked = True
		
		handle = New Label(Self)
		handle.internalMouseListener = internalSliderAdapter
		handle.internalMouseMotionListener = internalSliderAdapter
		handle.zOrderLocked = True
		
		buttonUpLeft.visible = False
		buttonDownRight.visible = False
		
		ApplySkin()
	End

' Public methods
	Method SnapToValue:Int(val:Int)
		If val < minValue Then
			val = minValue
			Return val
		End
		If val > maxValue Then
			val = maxValue
			Return val
		End
		If val Mod tickInterval = 0 Then Return val
		If val Mod tickInterval < tickInterval / 2 Then
			val -= val Mod tickInterval
		Else
			val -= val Mod tickInterval
			val += tickInterval
		End
		Return val
	End
	
	Method AdjustValue:Bool(amount:Int)
		If amount = 0 Then Return False
		Local oldValue:Int = value
		
		' snap if we must
		If value Mod tickInterval > 0 Then
			If amount < 0 Then
				value += tickInterval - (value Mod tickInterval)
			Else
				value -= value Mod tickInterval
			End
		End
		
		' adjust it
		value += amount * tickInterval

		' check that it's in range
		If value < minValue Then value = minValue
		If value > maxValue Then value = maxValue
		
		' if it changed, update the layout and fire an event
		If value <> oldValue Then
			Layout()
			FireActionPerformed(Self, ACTION_VALUE_CHANGED)
		End
		Return value <> oldValue
	End

' Overrides Component
	Method GetStyle:ComponentStyle(name:String)
		If name = "leftButton" Then
			Return styleLeftButton
		ElseIf name = "rightButton" Then
			Return styleRightButton
		ElseIf name = "topButton" Then
			Return styleTopButton
		ElseIf name = "bottomButton" Then
			Return styleBottomButton
		ElseIf name = "horizontalHandle" Then
			Return styleHorizontalHandle
		ElseIf name = "verticalHandle" Then
			Return styleVerticalHandle
		ElseIf name = "horizontalBar" Then
			Return styleHorizontalBar
		ElseIf name = "verticalBar" Then
			Return styleVerticalBar
		End
		Return Super.GetStyle(name)
	End
	
	Method SetStyle:Void(name:String, style:ComponentStyle)
		If name = "leftButton" Then
			styleLeftButton = style
		ElseIf name = "rightButton" Then
			styleRightButton = style
		ElseIf name = "topButton" Then
			styleTopButton = style
		ElseIf name = "bottomButton" Then
			styleBottomButton = style
		ElseIf name = "horizontalHandle" Then
			styleHorizontalHandle = style
		ElseIf name = "verticalHandle" Then
			styleVerticalHandle = style
		ElseIf name = "horizontalBar" Then
			styleHorizontalBar = style
		ElseIf name = "verticalBar" Then
			styleVerticalBar = style
		Else
			Super.SetStyle(name, style)
		End
		UpdateStyles()
	End
	
	Method Layout:Void()
		Local startVal:Int, endVal:Int
		If showButtons Then
			If orientation = SLIDER_HORIZONTAL Then
				buttonUpLeft.SetBounds(0, 0, leftButtonWidth, leftButtonHeight)
				buttonDownRight.SetBounds(Self.w - rightButtonWidth, 0, rightButtonWidth, rightButtonHeight)
				bar.SetBounds(buttonUpLeft.w, 0, Self.w - leftButtonWidth - rightButtonWidth, Self.h)
				startVal = leftButtonWidth+handleMargin
				endVal = startVal + bar.w - 2*handleMargin
			Else
				buttonUpLeft.SetBounds(0, 0, topButtonWidth, topButtonHeight)
				buttonDownRight.SetBounds(0, Self.h - bottomButtonHeight, bottomButtonWidth, bottomButtonHeight)
				bar.SetBounds(0, buttonUpLeft.h, Self.w, Self.h - topButtonHeight - bottomButtonHeight)
				startVal = topButtonHeight+handleMargin
				endVal = startVal + bar.h - 2*handleMargin
			End
			buttonUpLeft.visible = True
			buttonDownRight.visible = True
		Else
			buttonUpLeft.visible = False
			buttonDownRight.visible = False
		End
		Local fraction:Float = Float(value-minValue)/Float(maxValue-minValue)
		Local currentVal:Int
		If orientation = SLIDER_HORIZONTAL Then
			currentVal = startVal + (endVal - startVal) * fraction
			handle.SetBounds(currentVal-handleSize/2, 0, handleSize, Self.h)
		Else
			currentVal = startVal + (endVal - startVal) * fraction
			handle.SetBounds(0, currentVal-handleSize/2, Self.w, handleSize)
		End
	End
	
	Method GetSkinNodeName:String()
		Return SKIN_NODE_SLIDER
	End
End ' Class Slider



' private internal classes
Private

' handles all the internal events for the Slider component
Class InternalSliderAdapter Implements IActionListener, IMouseListener, IMouseMotionListener
Private
' Private fields
	Field slider:Slider
	
Public
' Constructors
	Method New(slider:Slider)
		Self.slider = slider
	End
	
' Implements IActionListener
	Method ActionPerformed:Void(source:Component, action:String)
		If source = slider.buttonUpLeft And action = ACTION_CLICKED Then
			If slider.direction = Slider.SLIDER_DIRECTION_TL_TO_BR Then
				slider.AdjustValue(-1)
			Else
				slider.AdjustValue(1)
			End
		ElseIf source = slider.buttonDownRight And action = ACTION_CLICKED Then
			If slider.direction = Slider.SLIDER_DIRECTION_TL_TO_BR Then
				slider.AdjustValue(1)
			Else
				slider.AdjustValue(-1)
			End
		End
	End
	
' Implements IMouseListener
	Method MousePressed:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		If slider.dragging Then Return
		slider.dragging = True
		If source = slider.handle Then
			slider.DoDrag(slider.handle.x + x, slider.handle.y + y)
		ElseIf source = slider.bar Then
			slider.DoDrag(slider.bar.x + x, slider.bar.y + y)
		End
	End
	
	Method MouseReleased:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		slider.dragging = False
		If source = slider.handle Then
			slider.DoDrag(slider.handle.x + x, slider.handle.y + y)
		ElseIf source = slider.bar Then
			slider.DoDrag(slider.bar.x + x, slider.bar.y + y)
		End
	End
	
	Method MouseClicked:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseEntered:Void(source:Component, x:Int, y:Int, exitedComp:Component, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseExited:Void(source:Component, x:Int, y:Int, enteredComp:Component, absoluteX:Int, absoluteY:Int)
	End
	
' Implements IMouseMotionListener
	Method MouseMoved:Void(source:Component, x:Int, y:Int, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseDragged:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		If Not slider.dragging Then Return
		If source = slider.handle Then
			slider.DoDrag(slider.handle.x + x, slider.handle.y + y)
		ElseIf source = slider.bar Then
			slider.DoDrag(slider.bar.x + x, slider.bar.y + y)
		End
	End
End ' Class InternalSliderAdapter

' handles all the internal events for the Window component
Class InternalWindowAdapter Implements IActionListener, IMouseListener, IMouseMotionListener
Private
' Private fields
	Field window:Window

Public
' Constructors
	Method New(window:Window)
		Self.window = window
	End

' Implements IActionListener
	Method ActionPerformed:Void(source:Component, action:String)
		If source = window.closeButton And action = ACTION_CLICKED Then
			window.Dispose()
		ElseIf source = window.maximizeButton And action = ACTION_CLICKED Then
			window.Maximized = Not window.Maximized
			' clear out mouse hover/down after maximising/restoring
			' should probably do this in a common place but... >_>
			window.maximizeButton.mouseHover = False
			window.maximizeButton.mouseDown = False
		ElseIf source = window.minimizeButton And action = ACTION_CLICKED Then
			'window.Minimized = True ' TODO: disabled until positioning of minimised windows is done
		ElseIf source = window.shadeButton And action = ACTION_CLICKED Then
			window.Shaded = Not window.Shaded
		End
	End
	
' Implements IMouseListener
	Method MousePressed:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		If window.dragging Then Return
		If window.maximized Or window.minimized Then Return
		window.dragging = True
		window.dragX = absoluteX
		window.dragY = absoluteY
		window.originalX = window.x
		window.originalY = window.y
	End
	
	Method MouseReleased:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		window.dragging = False
	End
	
	Method MouseClicked:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseEntered:Void(source:Component, x:Int, y:Int, exitedComp:Component, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseExited:Void(source:Component, x:Int, y:Int, enteredComp:Component, absoluteX:Int, absoluteY:Int)
	End

' Implements IMouseMotionListener
	Method MouseMoved:Void(source:Component, x:Int, y:Int, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseDragged:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		If window.maximized Or window.minimized Then window.dragging = False
		If Not window.dragging Then Return
		Local dx:Int = absoluteX-window.dragX, dy:Int = absoluteY-window.dragY
		Local newX:Int = window.originalX + dx, newY:Int = window.originalY + dy
		
		If GUIDesktop(window.parent) <> Null And GUIDesktop(window.parent).restrictWindows Then
			If newX + window.w > window.parent.w Then newX = window.parent.w - window.w
			If newY + window.h > window.parent.h Then newY = window.parent.h - window.h
			If newX < 0 Then newX = 0
			If newY < 0 Then newY = 0
		End
		window.SetLocation(newX, newY)
	End
End ' Class InternalWindowAdapter

' handles all the internal events for the Button component
Class InternalButtonAdapter Implements IMouseListener
Private
' Private fields
	Field button:Button
	
' Private methods
	Method DoClick:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
		' is it a radio button?
		If RadioButton(Self.button) <> Null And RadioButton(Self.button).radioGroup <> Null Then
			RadioButton(Self.button).radioGroup.SelectedButton = RadioButton(Self.button)
		' is it a toggle button?
		ElseIf Self.button.toggle Then
			Self.button.selected = Not Self.button.selected
		End
		Self.button.FireActionPerformed(Self.button, ACTION_CLICKED)
	End
	
Public
' Constructors
	Method New(button:Button)
		Self.button = button
	End
	
' Implements IMouseListener
	Method MouseClicked:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
#If TARGET="ios" Or TARGET="android" Then
		If Self.button.simpleNormalImage = Null Then DoClick(source, x, y, button, absoluteX, absoluteY)
#Else
		DoClick(source, x, y, button, absoluteX, absoluteY)
#End
	End
	
	Method MousePressed:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
#If TARGET="ios" Or TARGET="android" Then
		If Self.button.simpleNormalImage <> Null Then DoClick(source, x, y, button, absoluteX, absoluteY)
#End
	End
	
	Method MouseReleased:Void(source:Component, x:Int, y:Int, button:Int, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseEntered:Void(source:Component, x:Int, y:Int, exitedComp:Component, absoluteX:Int, absoluteY:Int)
	End
	
	Method MouseExited:Void(source:Component, x:Int, y:Int, enteredComp:Component, absoluteX:Int, absoluteY:Int)
	End
End ' Class InternalButtonAdapter

' This function exists to prevent developers from instantiating a component without a parent (since Monkey always provides an empty default constructor)
Function NoParent:Void()
	AssertError("Must pass a parent component.")
End

Function CheckMidhandle:Void(img:GameImage)
	If img.MidHandle Then
		AssertError("Components may not use a midhandled image.")
	End
End
